/*
 * Copyright (c) 2010, Katholieke Universiteit Leuven
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Katholieke Universiteit Leuven nor the names of
 *       its contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>

#include "../api/events.h"

#define LOOCI_EVENTS_PORT "5555"

#define MIN(a,b) (a < b ? a : b)

struct net_event {
  uint8_t type;
  uint8_t source_cid;
  char payload[LOOCI_EVENT_PAYLOAD_MAXLEN];
};

static int get_receiving_socket(char * port) {
  int status;
  struct addrinfo hints;
  struct addrinfo * servinfo;
  struct addrinfo * p;
  int sockfd;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_INET6; // We use IPv6
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_flags = AI_PASSIVE;
  if((status = getaddrinfo(NULL, port, &hints, &servinfo))!=0) {
    fprintf(stderr, "getaddrinfo error: %s\r\n", gai_strerror(status));
    return -1;
  }

  for(p = servinfo; p != NULL; p = p->ai_next) {
    if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
      continue;
    }
    if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      continue;
    }
    break;
  }

  if(p == NULL) sockfd = -1;  
  freeaddrinfo(servinfo);
  return sockfd;
}

bool looci_receive_event(struct looci_event * event) {
  // open a UDP socket for receiving an event
  struct sockaddr_storage their_addr;
  socklen_t addr_len;
  int numbytes;
  struct net_event read_event;
  int sockfd = get_receiving_socket(LOOCI_EVENTS_PORT);
  if(sockfd == -1) {
    return false;
  }
  // blocking read from the socket
  addr_len = sizeof(their_addr);
  memset(&read_event, 0, sizeof(struct net_event));
  numbytes = recvfrom(sockfd, &read_event, sizeof(struct net_event), 0, (struct sockaddr *)&their_addr, &addr_len);
  // close the socket, we don't need it anymore
  close(sockfd);
  // copy incoming event to passed struct
  if(numbytes == -1) {
    return false;
  }
  event->type = read_event.type;
  inet_ntop(their_addr.ss_family, &(((struct sockaddr_in6 *)&their_addr)->sin6_addr), event->source_node, sizeof(event->source_node));
  event->source_cid = read_event.source_cid;
  event->len = numbytes - 2; // payload length is length of net_event - 2
  memcpy(event->payload, read_event.payload, LOOCI_EVENT_PAYLOAD_MAXLEN);
}

static int get_sending_socket(char * addr, char * port) {
  int status;
  struct addrinfo hints;
  struct addrinfo * servinfo;
  struct addrinfo * p;
  int sockfd;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_INET6; // We use IPv6
  hints.ai_socktype = SOCK_DGRAM;
  if((status = getaddrinfo(addr, port, &hints, &servinfo))!=0) {
    fprintf(stderr, "getaddrinfo error: %s\r\n", gai_strerror(status));
    return -1;
  }

  for(p = servinfo; p != NULL; p = p->ai_next) {
    if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
      continue;
    }
    if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      continue;
    }
    break;
  }

  if(p == NULL) sockfd = -1;  
  freeaddrinfo(servinfo);
  return sockfd;
}

bool looci_send_event_to(char * destinationNodeID, uint8_t type, uint8_t len, void * payload) {
  // get the socket
  int sockfd = get_sending_socket(destinationNodeID, LOOCI_EVENTS_PORT);
  if(sockfd == -1) {
    return false;
  }
  // fill the net_event
  struct net_event event;
  event.type = type;
  event.source_cid = 1; // Host app is only 'component', so give it id 1
  memcpy(event.payload, payload, MIN(LOOCI_EVENT_PAYLOAD_MAXLEN, len));
  // send
  int numbytes = MIN(LOOCI_EVENT_PAYLOAD_MAXLEN, len) + 2;
  int sent = send(sockfd, &event, numbytes , 0);
  close(sockfd);
  if(sent == numbytes) {
    return true;
  } else {
    return false;
  }
}



